package rtcsdp

import (
	"crypto/rand"
	"encoding/binary"
	"fmt"
	"net/url"
	"strings"
	"time"

	"github.com/pion/sdp/v3"
)

type Session struct {
	URI    *url.URL
	Title  string
	Timing Timing

	sessionId      uint64
	sessionVersion uint64

	Medias []*Media
}

func Unmarshal(data []byte) (*Session, error) {
	var ssd sdp.SessionDescription
	if err := ssd.Unmarshal(data); err != nil {
		return nil, err
	}

	var session Session
	if err := session.Unmarshal(&ssd); err != nil {
		return nil, err
	}

	return &session, nil
}

func (d *Session) Unmarshal(ssd *sdp.SessionDescription) error {
	d.URI = ssd.URI
	d.sessionId = ssd.Origin.SessionID
	d.sessionVersion = ssd.Origin.SessionVersion
	d.Title = strings.TrimSpace(string(ssd.SessionName))
	for _, td := range ssd.TimeDescriptions {
		d.Timing.StartTime = td.Timing.StartTime
		d.Timing.StopTime = td.Timing.StopTime
	}

	d.Medias = make([]*Media, 0, len(ssd.MediaDescriptions))

	for i, md := range ssd.MediaDescriptions {
		var m Media
		if err := m.Unmarshal(md); err != nil {
			return fmt.Errorf("media %d is invalid: %v", i+1, err)
		}

		d.Medias = append(d.Medias, &m)
	}

	return nil
}

func (d Session) Marshal() *sdp.SessionDescription {
	cryptoUint64 := func() (uint64, error) {
		var v uint64
		if err := binary.Read(rand.Reader, binary.LittleEndian, &v); err != nil {
			return 0, err
		}
		return v, nil
	}
	if d.sessionId == 0 {
		d.sessionId, _ = cryptoUint64()
	}
	if d.sessionVersion == 0 {
		d.sessionVersion = uint64(time.Now().Unix())
	}

	if len(d.Title) == 0 {
		// RFC 4566: If a session has no meaningful name, the
		// value "s= " SHOULD be used (i.e., a single space as the session name).
		d.Title = " "
	}

	var address = "0.0.0.0"

	ret := &sdp.SessionDescription{
		Version: 0,
		Origin: sdp.Origin{
			Username:       "-",
			SessionID:      d.sessionId,
			SessionVersion: d.sessionVersion,
			NetworkType:    "IN",
			AddressType:    "IP4",
			UnicastAddress: "127.0.0.1",
		},
		SessionName:        sdp.SessionName(d.Title),
		SessionInformation: nil,
		URI:                d.URI,
		EmailAddress:       nil,
		PhoneNumber:        nil,
		ConnectionInformation: &sdp.ConnectionInformation{
			NetworkType: "IN",
			AddressType: "IP4",
			Address: &sdp.Address{
				Address: address,
			},
		},
		Bandwidth: nil,
		TimeDescriptions: []sdp.TimeDescription{
			{
				Timing: sdp.Timing{
					StartTime: d.Timing.StartTime,
					StopTime:  d.Timing.StopTime,
				},
			},
		},
		TimeZones:         nil,
		EncryptionKey:     nil,
		Attributes:        nil,
		MediaDescriptions: []*sdp.MediaDescription{},
	}

	for _, media := range d.Medias {
		ret.WithMedia(media.Marshal())
	}

	return ret
}

type Timing struct {
	StartTime uint64
	StopTime  uint64
}
